﻿using System;
using System.Collections.Generic;
using GeoAPI;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Geometries;
using YJKGridTopologyCal.Geometries.Implementation;

namespace YJKGridTopologyCal
{
	public class NtsGeometryServices : IGeometryServices
	{
		public static IGeometryServices Instance
		{
			get
			{
				object lockObject = NtsGeometryServices.LockObject1;
				IGeometryServices instance;
				lock (lockObject)
				{
					if (NtsGeometryServices._instance != null)
					{
						instance = NtsGeometryServices._instance;
					}
					else
					{
						object lockObject2 = NtsGeometryServices.LockObject2;
						lock (lockObject2)
						{
							NtsGeometryServices._instance = new NtsGeometryServices();
						}
						instance = NtsGeometryServices._instance;
					}
				}
				return instance;
			}
			set
			{
				if (value == null)
				{
					return;
				}
				object lockObject = NtsGeometryServices.LockObject1;
				lock (lockObject)
				{
					NtsGeometryServices._instance = value;
				}
			}
		}

		public NtsGeometryServices() : this(CoordinateArraySequenceFactory.Instance, new PrecisionModel(PrecisionModels.Floating), -1)
		{
		}

		public NtsGeometryServices(ICoordinateSequenceFactory coordinateSequenceFactory, IPrecisionModel precisionModel, int srid)
		{
			this.DefaultCoordinateSequenceFactory = coordinateSequenceFactory;
			this.DefaultPrecisionModel = precisionModel;
			this.DefaultSRID = srid;
		}

		public int DefaultSRID { get; set; }

		public ICoordinateSequenceFactory DefaultCoordinateSequenceFactory { get; private set; }

		public IPrecisionModel DefaultPrecisionModel { get; private set; }

		public IPrecisionModel CreatePrecisionModel(PrecisionModels modelType)
		{
			return new PrecisionModel(modelType);
		}

		public IPrecisionModel CreatePrecisionModel(IPrecisionModel precisionModel)
		{
			if (precisionModel is PrecisionModel)
			{
				return new PrecisionModel((PrecisionModel)precisionModel);
			}
			if (!precisionModel.IsFloating)
			{
				return new PrecisionModel(precisionModel.Scale);
			}
			return new PrecisionModel(precisionModel.PrecisionModelType);
		}

		public IPrecisionModel CreatePrecisionModel(double scale)
		{
			return new PrecisionModel(scale);
		}

		public IGeometryFactory CreateGeometryFactory()
		{
			return this.CreateGeometryFactory(this.DefaultSRID);
		}

		public IGeometryFactory CreateGeometryFactory(int srid)
		{
			return this.CreateGeometryFactory(this.DefaultPrecisionModel, srid, this.DefaultCoordinateSequenceFactory);
		}

		public void ReadConfiguration()
		{
			object lockObject = NtsGeometryServices.LockObject1;
			lock (lockObject)
			{
			}
		}

		public void WriteConfiguration()
		{
			object lockObject = NtsGeometryServices.LockObject2;
			lock (lockObject)
			{
			}
		}

		public IGeometryFactory CreateGeometryFactory(ICoordinateSequenceFactory coordinateSequenceFactory)
		{
			return this.CreateGeometryFactory(this.DefaultPrecisionModel, this.DefaultSRID, coordinateSequenceFactory);
		}

		public IGeometryFactory CreateGeometryFactory(IPrecisionModel precisionModel)
		{
			return this.CreateGeometryFactory(precisionModel, this.DefaultSRID, this.DefaultCoordinateSequenceFactory);
		}

		public IGeometryFactory CreateGeometryFactory(IPrecisionModel precisionModel, int srid)
		{
			return this.CreateGeometryFactory(precisionModel, srid, this.DefaultCoordinateSequenceFactory);
		}

		public IGeometryFactory CreateGeometryFactory(IPrecisionModel precisionModel, int srid, ICoordinateSequenceFactory coordinateSequenceFactory)
		{
			if (precisionModel == null)
			{
				throw new System.ArgumentNullException("precisionModel");
			}
			if (coordinateSequenceFactory == null)
			{
				throw new System.ArgumentNullException("coordinateSequenceFactory");
			}
			NtsGeometryServices.GeometryFactoryKey key = new NtsGeometryServices.GeometryFactoryKey(precisionModel, coordinateSequenceFactory, srid);
			IGeometryFactory result;
			if (!this._factories.TryGetValue(key, out result) && !this._factories.TryGetValue(key, out result))
			{
				result = new GeometryFactory(precisionModel, srid, coordinateSequenceFactory);
				this._factories.Add(key, ref result);
			}
			return result;
		}

		public int NumFactories
		{
			get
			{
				object factoriesLock = this._factoriesLock;
				int count;
				lock (factoriesLock)
				{
					count = this._factories.Count;
				}
				return count;
			}
		}

		private static volatile IGeometryServices _instance;

		private static readonly object LockObject1 = new object();

		private static readonly object LockObject2 = new object();

		private readonly object _factoriesLock = new object();

		private readonly NtsGeometryServices.SaveDictionary<NtsGeometryServices.GeometryFactoryKey, IGeometryFactory> _factories = new NtsGeometryServices.SaveDictionary<NtsGeometryServices.GeometryFactoryKey, IGeometryFactory>();

		private struct GeometryFactoryKey
		{
			public GeometryFactoryKey(IPrecisionModel precisionModel, ICoordinateSequenceFactory factory, int srid)
			{
				this._precisionModel = precisionModel;
				this._factory = factory;
				this._srid = srid;
			}

			public override int GetHashCode()
			{
				return 889377 ^ this._srid ^ this._precisionModel.GetHashCode() ^ this._factory.GetHashCode();
			}

			public override bool Equals(object obj)
			{
				if (!(obj is NtsGeometryServices.GeometryFactoryKey))
				{
					return false;
				}
				NtsGeometryServices.GeometryFactoryKey geometryFactoryKey = (NtsGeometryServices.GeometryFactoryKey)obj;
				return this._srid == geometryFactoryKey._srid && this._precisionModel.Equals(geometryFactoryKey._precisionModel) && this._factory.Equals(geometryFactoryKey._factory);
			}

			private readonly IPrecisionModel _precisionModel;

			private readonly ICoordinateSequenceFactory _factory;

			private readonly int _srid;
		}

		private class SaveDictionary<TKey, TValue>
		{
			public void Add(TKey key, ref TValue value)
			{
				object @lock = this._lock;
				lock (@lock)
				{
					if (!this._dictionary.ContainsKey(key))
					{
						this._dictionary.Add(key, value);
					}
				}
			}

			public bool TryGetValue(TKey key, out TValue value)
			{
				object @lock = this._lock;
				bool result;
				lock (@lock)
				{
					result = this._dictionary.TryGetValue(key, out value);
				}
				return result;
			}

			public int Count
			{
				get
				{
					object @lock = this._lock;
					int count;
					lock (@lock)
					{
						count = this._dictionary.Count;
					}
					return count;
				}
			}

			private readonly object _lock = new object();

			private readonly Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
		}
	}
}
